package com.xiaoyu.shenyou.shenyouv1.websocketclient.autobettingclient;


import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.xiaoyu.shenyou.shenyouv1.common.position.PositionUtil;
import com.xiaoyu.shenyou.shenyouv1.common.roomname.RoomName;
import com.xiaoyu.shenyou.shenyouv1.common.shenyoucode.ToClientCode;
import com.xiaoyu.shenyou.shenyouv1.common.shenyoucode.ToServerCode;
import com.xiaoyu.shenyou.shenyouv1.common.shenyouurl.ShenyouURL;
import com.xiaoyu.shenyou.shenyouv1.entity.AutoConfigEntity;
import com.xiaoyu.shenyou.shenyouv1.entity.AutoLogEntity;
import com.xiaoyu.shenyou.shenyouv1.entity.AutoUserEntity;
import com.xiaoyu.shenyou.shenyouv1.entity.PredictionEntity;
import com.xiaoyu.shenyou.shenyouv1.mapper.AutoPlayMapper;
import com.xiaoyu.shenyou.shenyouv1.mapper.PredictionMapper;
import com.xiaoyu.shenyou.shenyouv1.websocketclient.tocliententity.ToClientEntity;
import com.xiaoyu.shenyou.shenyouv1.websocketclient.tocliententity.betconfig.RoundInfo;
import com.xiaoyu.shenyou.shenyouv1.websocketclient.tocliententity.betting.BettingInfo;
import com.xiaoyu.shenyou.shenyouv1.websocketclient.tocliententity.jumpinfo.JumpRoom;
import com.xiaoyu.shenyou.shenyouv1.websocketclient.tocliententity.killroom.KillRoom;
import com.xiaoyu.shenyou.shenyouv1.websocketclient.tocliententity.roominfo.RoomInfo;
import com.xiaoyu.shenyou.shenyouv1.websocketclient.tocliententity.settleinfo.SettleInfo;
import com.xiaoyu.shenyou.shenyouv1.websocketclient.toserverentity.Position;
import com.xiaoyu.shenyou.shenyouv1.websocketclient.toserverentity.ToServerEntity;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.springframework.data.redis.core.RedisTemplate;

import java.net.URI;
import java.net.URISyntaxException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
public class BettingSocketClient extends WebSocketClient {

    private boolean isBetted = true;
    /**
     * 数量跳动
     * key房间号，value为数量
     */
    private Map<Integer, Double> bettingCount = new HashMap<Integer, Double>();
    private boolean isBettingFlag = false;
    private boolean flag = false;
    private boolean preBetting = false;
    private AutoLogEntity autoLog;
    private AutoConfigEntity autoConfig;
    private boolean isLogin = false;
    private AutoUserEntity autoUser;
    private AutoPlayMapper autoPlayMapper;
    private RedisTemplate<String, Object> redisTemplate;
    private PredictionMapper predictionMapper;
    /**
     * 维护socketMap
     */
    public static final Map<Integer, BettingSocketClient> WEBSOCKET_CLIENT_MAP = new ConcurrentHashMap<>();

    {
        bettingCount.put(1, 0.0);
        bettingCount.put(2, 0.0);
        bettingCount.put(3, 0.0);
        bettingCount.put(4, 0.0);

        bettingCount.put(5, 0.0);
        bettingCount.put(6, 0.0);
        bettingCount.put(7, 0.0);
        bettingCount.put(8, 0.0);
    }

    public BettingSocketClient(AutoUserEntity autoUser, AutoPlayMapper autoPlayMapper, RedisTemplate<String, Object> redisTemplate, PredictionMapper predictionMapper) throws URISyntaxException {
        super(new URI(ShenyouURL.WEBSOCKET_URL));
        this.autoPlayMapper = autoPlayMapper;
        this.redisTemplate = redisTemplate;
        this.autoUser = autoUser;
        this.predictionMapper = predictionMapper;
    }


    @Override
    public void onOpen(ServerHandshake handshake) {
        log.info("连接成功，发送登录消息");
        //发送登录信息
        ToServerEntity toServerEntity = new ToServerEntity();
        toServerEntity.setCode(ToServerCode.LOGIN).setToken(autoUser.getToken());
        this.send(JSONUtil.toJsonStr(toServerEntity));
    }

    @Override
    public void onMessage(String message) {
        ToClientEntity toClient = JSONUtil.toBean(message, ToClientEntity.class);
        /**
         * 1.登录成功
         */
        if (toClient.getCode() == ToClientCode.AUTH) {
            //登录成功返回
            log.info("1.登录成功，发送登录信息后返回的结果，{}", toClient);
            this.autoLog = new AutoLogEntity();
            this.autoLog.setWin_count(0)
                    .setProfitAndLose(0)
                    .setProfitAndLose(0)
                    .setUser_id(this.autoUser.getUser_id())
                    .setContinue_fail(0)
                    .setMultiple_reset(0)
                    .setBalance(0)
                    .setContinue_fail_skip(0);
            this.autoConfig = autoPlayMapper.selectAutoConfigById(this.autoUser.getUser_id());
            isLogin = true;
            //发送当前位置
            //登录成功，发送位置信息
            ToServerEntity toServerPosition = new ToServerEntity();
            toServerPosition.setCode(ToServerCode.SEND_POSITION)
                    .setGameType(2)
                    .setUid(this.autoUser.getUser_id())
                    .setRoomId(0)
                    .setPosition(new Position().setX(50).setY(50));
            this.send(JSONUtil.toJsonStr(toServerPosition));
            return;
        }

        /**
         * 2.1期数和倒计时
         */
        if (toClient.getCode() == ToClientCode.GAMES) {
            log.info(" 2.1期数和倒计时，{}", toClient);
            RoundInfo roundInfo = JSONUtil.toBean(toClient.getData(), RoundInfo.class);
            this.autoLog.setGames(roundInfo.getRound());
            return;
        }

        /**
         * 所有人物信息
         */
        if (toClient.getCode() == ToClientCode.ALL_PERSON_INFO) {
            preBetting = true;
            log.info("所有人物之后下注，preBetting：{}", preBetting);
            return;
        }

        /**
         * 3.选房和下注
         */
        if (!isBetted) {
            //标识当前本局有无下过
            if (this.isLogin && this.flag && preBetting) {
                log.info(" 3，{}用户成功连接上!进入就绪状态!", this.autoUser.getUser_id());
                //每次下注查询出下注配置
                this.autoConfig = autoPlayMapper.selectAutoConfigById(this.autoUser.getUser_id());
                if (stop()) return;
                selectRoom(this.autoConfig, this.autoLog);
                betting(this.autoConfig, this.autoLog);
                //执行完修改本局已下注的标识
            }
        }

        /**
         * 返回选择的房间信息
         */
        if (toClient.getCode() == ToClientCode.SELECT_ROOM) {
            JumpRoom jumpRoom = JSONUtil.toBean(toClient.getData(), JumpRoom.class);
            if (jumpRoom.getUid() == this.autoUser.getUser_id() && jumpRoom.getRoomId() != 0) {
                this.autoLog.setPrediction(RoomName.getRoomNameById.get(jumpRoom.getRoomId()));
            }
            return;
        }

        /**
         * 下注跳动信息
         */
        if (toClient.getCode() == ToClientCode.ALL_ROOM_BET_INFO) {
            if (this.isLogin && this.isBettingFlag && this.autoConfig.getIs_skip_betting() == 1) {
                JSONArray array = JSONUtil.parseArray(toClient.getData());
                List<RoomInfo> roomInfos = array.toList(RoomInfo.class);
                roomInfos.stream().distinct().forEach(roomInfo -> bettingCount.put(roomInfo.getRoomId(), roomInfo.getTotalAmount()));
                //跳动下注
                List<Map.Entry<Integer, Double>> collect = bettingCount.entrySet().stream()
                        .distinct()
                        .sorted(((o1, o2) -> (int) ((o2.getValue() - o1.getValue()) * 10)))
                        .collect(Collectors.toList());
                Integer roomId = collect.get(this.autoConfig.getSkip_betting() - 1).getKey();
                ToServerEntity toServerEntity = new ToServerEntity();
                toServerEntity.setCode(ToServerCode.SELECT_ROOM)
                        .setRoomId(roomId)
                        .setUid(autoUser.getUser_id())
                        .setGameType(2)
                        .setPosition(PositionUtil.getPositionById.get(roomId));
                this.send(JSONUtil.toJsonStr(toServerEntity));
            }
            return;
        }

        /**
         * 5.返回余额
         */
        if (toClient.getCode() == ToClientCode.RETURN_BALANCE) {
            this.autoLog.setBalance(Double.parseDouble(toClient.getData()));
            //余额不够投入,结束
            if (this.autoConfig.getSize() > this.autoLog.getBalance()) {
                //余额不足
                log.info("余额不够投入,结束！");
                autoUser.setIs_auto(0);
                autoPlayMapper.updateAutoUserIsAuto(autoUser);
                BettingSocketClient.WEBSOCKET_CLIENT_MAP.remove(autoUser.getUser_id());
                this.close();
            }
            return;
        }

        /**
         * 6.下注返回下注信息
         */
        if (toClient.getCode() == ToClientCode.BETTING_SUCCESS) {
            log.info("6.下注返回下注信息：{}，", toClient);
            BettingInfo bettingInfo = JSONUtil.toBean(toClient.getData(), BettingInfo.class);
            this.autoLog.setBetting(bettingInfo.getAmount());
            return;
        }

        /**
         *  7.被噶的房间
         */
        if (toClient.getCode() == ToClientCode.KILL_ROOM) {
            KillRoom killerRoom = JSONUtil.toBean(toClient.getData(), KillRoom.class);
            //确保每一局为0开始
            String killRoom = RoomName.getRoomNameById.get(killerRoom.getKillRoomId());
            log.info("7.失败的房间：{}", killRoom);
            this.autoLog.setKill_room(killRoom)
                    .setResult(killRoom.equals(this.autoLog.getPrediction()) ? "失败" : "成功");
            log.info("结果:{}", this.autoLog.getResult());
            return;
        }

        /**
         * 8.胜利结算信息
         */
        if (toClient.getCode() == ToClientCode.SETTLE_INFO) {
            log.info("8，成功结算信息，{}", toClient);
            SettleInfo settleInfo = JSONUtil.toBean(toClient.getData(), SettleInfo.class);
            //未下注
            if (settleInfo.getInputAmount() != 0) {
                //成功
                //胜利清0
                this.autoLog.setBetting(settleInfo.getInputAmount())
                        .setContinue_fail(0)
                        .setMultiple_reset(0)
                        .setWin_count(this.autoLog.getWin_count() + 1)
                        .setIncome(settleInfo.getAmount())
                        .setContinue_fail_skip(0)
                        .setBetting(settleInfo.getInputAmount())
                        .setProfitAndLose(this.autoLog.getProfitAndLose() + (this.autoLog.getIncome() - this.autoLog.getBetting()));
                wtriting();
            }
            return;
        }

        /**
         * 失败
         */
        if (toClient.getCode() == ToClientCode.FAIL_INFO) {
            log.info("8，失败结算信息，{}", toClient);
            SettleInfo settleInfo = JSONUtil.toBean(toClient.getData(), SettleInfo.class);
            if (settleInfo.getInputAmount() != 0) {
                //失败
                //非轮空失败后是设置跳过
                this.autoLog.setIncome(0)
                        .setWin_count(0)
                        .setContinue_fail(this.autoLog.getContinue_fail() + 1)
                        .setMultiple_reset(this.autoLog.getMultiple_reset() + 1)
                        .setContinue_fail_skip(this.autoConfig.getContinue_fail_skip())
                        .setBetting(settleInfo.getInputAmount())
                        .setProfitAndLose(this.autoLog.getProfitAndLose() - this.autoLog.getBetting());
                //重置记录失败
                if (autoLog.getMultiple_reset() >= this.autoConfig.getMultiple_reset()) {
                    this.autoLog.setMultiple_reset(0);
                }
                wtriting();

            }
            return;
        }

        /**
         * 9.0结束
         */
        if (toClient.getCode() == ToClientCode.GAME_OVER) {
            log.info("9.0结束,{}", toClient);
            isBettingFlag = false;
            preBetting = false;
            //发送位置信息
            ToServerEntity toServerPosition = new ToServerEntity();
            toServerPosition.setCode(ToServerCode.SEND_POSITION)
                    .setGameType(2)
                    .setUid(this.autoUser.getUser_id())
                    .setRoomId(0)
                    .setPosition(new Position().setX(50).setY(50));
            this.send(JSONUtil.toJsonStr(toServerPosition));
            return;
        }
    }


    private boolean stop() {
        if (this.autoLog.getContinue_fail() >= this.autoConfig.getContinue_fail()) {
            autoUser.setIs_auto(0);
            autoPlayMapper.updateAutoUserIsAuto(autoUser);
            log.info("用户{}连败停止", autoUser.getUser_id());
            //关闭,且删除该key
            BettingSocketClient.WEBSOCKET_CLIENT_MAP.remove(autoUser.getUser_id());
            this.close();
            return true;
        }
        if (this.autoLog.getWin_count() >= this.autoConfig.getWincount_stop()) {
            autoUser.setIs_auto(0);
            autoPlayMapper.updateAutoUserIsAuto(autoUser);
            log.info("用户{}连胜停止", autoUser.getUser_id());
            //关闭,且删除该key
            BettingSocketClient.WEBSOCKET_CLIENT_MAP.remove(autoUser.getUser_id());
            this.close();
            return true;
        }

        double profitAndLose = this.autoLog.getProfitAndLose();
        if (profitAndLose >= this.autoConfig.getProfit()) {
            autoUser.setIs_auto(0);
            autoPlayMapper.updateAutoUserIsAuto(autoUser);
            this.close();
            log.info("用户{}盈利停止", autoUser.getUser_id());
            //关闭,且删除该key
            BettingSocketClient.WEBSOCKET_CLIENT_MAP.remove(autoUser.getUser_id());
            this.close();
            return true;
        }
        if (profitAndLose <= -this.autoConfig.getLose()) {
            autoUser.setIs_auto(0);
            autoPlayMapper.updateAutoUserIsAuto(autoUser);
            log.info("用户{}亏本停止", autoUser.getUser_id());
            //关闭,且删除该key
            BettingSocketClient.WEBSOCKET_CLIENT_MAP.remove(autoUser.getUser_id());
            this.close();
            return true;
        }
        return false;
    }

    private void wtriting() {
        //下注信息结算，写入auto_log中
        autoPlayMapper.insertAutoLogByUserId(this.autoLog);
        //数据库查出所有的该用户的记录，展示100条
        List<AutoLogEntity> autoLogs = autoPlayMapper.selectAutoLogsBySize(autoUser.getUser_id(), 100);
        //写入缓存
        redisTemplate.opsForValue().set("auto_logs" + autoUser.getUser_id(), autoLogs);
        redisTemplate.expire("auto_logs" + autoUser.getUser_id(), 1, TimeUnit.MINUTES);
    }


    private void selectRoom(AutoConfigEntity autoConfig, AutoLogEntity autoLog) {
        /**
         * 选房间
         */
        if (autoLog.getContinue_fail_skip() > 0) {
            autoLog.setContinue_fail_skip(autoLog.getContinue_fail_skip() - 1);
            //不进行下注,停止,输了进行停止才不下注
            log.info("跳过当前期，continue_fail_skip");
            return;
        }
        PredictionEntity prediction = predictionMapper.selectLastMethods(autoConfig.getPrediction_name());
        log.info("选房,算法名字:{},预测房间:{}", prediction.getName(), prediction.getPrediction_room());
        autoLog.setPrediction(prediction.getPrediction_room());
        //房间名字获取id
        Integer roomId = RoomName.getIdByRoomName.get(prediction.getPrediction_room());
        if (ObjectUtil.isNull(roomId)) {
            //说明本局轮空
            log.info("本局轮空");
            return;
        }
        ToServerEntity toServerEntity = new ToServerEntity();
        toServerEntity.setCode(ToServerCode.SELECT_ROOM)
                .setGameType(2)
                .setUid(this.autoUser.getUser_id())
                .setRoomId(roomId)
                .setPosition(PositionUtil.getPositionById.get(roomId));
        this.send(JSONUtil.toJsonStr(toServerEntity));
        log.info("发送房间");
    }

    private void betting(AutoConfigEntity autoConfig, AutoLogEntity autoLog) {
        /**
         * 下注
         */
        //获取倍数
        int multiple = autoConfig.getMultiple();
        //进行下注
        double bettingSize = autoConfig.getSize() * Math.pow(multiple, autoLog.getMultiple_reset());
        log.info("开始下注，{}，", bettingSize);
        this.autoLog.setCreate_time(LocalDateTime.now());
        ToServerEntity toServerEntity = new ToServerEntity();
        toServerEntity.setCode(ToServerCode.BETTING).setGameType(2);
        while (true) {
            if (bettingSize >= 1 && bettingSize < 10) {
                int num = (int) bettingSize;
                for (int i = 0; i < num; i++) {
                    toServerEntity.setBetAmount(1.0);
                    bettingSize = bettingSize - 1;
                    this.send(JSONUtil.toJsonStr(toServerEntity));
                }
            }
            if (bettingSize >= 10) {
                int num = (int) bettingSize / 10;
                for (int i = 0; i < num; i++) {
                    toServerEntity.setBetAmount(10.0);
                    bettingSize = bettingSize - 10;
                    this.send(JSONUtil.toJsonStr(toServerEntity));
                }
            }
            if (bettingSize <= 0) {
                break;
            }
        }
        isBetted = true;
        flag = false;
        isBettingFlag = true;
        preBetting = false;
    }


    @Override
    public void onClose(int code, String reason, boolean remote) {
        //将当前id移除
        BettingSocketClient.WEBSOCKET_CLIENT_MAP.remove(autoUser.getUser_id());
        autoUser.setIs_auto(0);
        autoPlayMapper.updateAutoUserIsAuto(autoUser);
        log.info("user_id：{},websocket客户端退出连接:{}", this.autoUser.getUser_id(), reason);
    }

    @Override
    public void onError(Exception ex) {
        log.info("websocket客户端和服务器连接发生错误={}", ex.getMessage());
    }

    public void setBetted(boolean betted) {
        isBetted = betted;
    }

    public AutoLogEntity getAutoLog() {
        return autoLog;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }


}
